package com.demo.webapp.views.freemarker.directive.core;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.tuckey.web.filters.urlrewrite.utils.StringUtils;

import com.demo.webapp.config.Constants;
import com.demo.webapp.views.freemarker.FreemarkerManagerEx;
import com.demo.webapp.views.freemarker.directive.config.ConfigurationManager;
import com.demo.webapp.views.freemarker.directive.core.validation.ParameterValidateable;
import com.demo.webapp.views.freemarker.directive.model.DirectiveGroupModel;
import com.demo.webapp.views.freemarker.directive.model.DirectiveModel;

import freemarker.template.Configuration;
import freemarker.template.TemplateDirectiveModel;

/**
 * Spring的扩展FreemarkerManager对象
 * ①、使用Spring管理扩展指令对象</br>
 * ②、使用配置文件设置扩展指令模型
 * @author ryuu.kk
 *
 */
public class SpringFreemarkerManagerEx extends FreemarkerManagerEx {

	private static final Log Logger = LogFactory.getLog(SpringFreemarkerManagerEx.class);
	
	/**
	 * WebApplicationContext
	 */
	protected WebApplicationContext applicationContext;
	
	/**
	 * AutowireCapableBeanFactory
	 */
    protected AutowireCapableBeanFactory autoWiringFactory;
    
    private boolean useClassCache = true;
    
    private boolean alwaysRespectAutowireStrategy = false;
    
    private final Map<String, Object> classes = new HashMap<String, Object>();
    
    /** 写入策略 :默认写入策略是不使用任何写入策略,但@Autowired除外**/
    protected int autowireStrategy = AutowireCapableBeanFactory.AUTOWIRE_NO;
    
	/**
	 * 注册指令模型
	 * @param config Configuration
	 * @param servletContext ServletContext
	 * @throws Exception 
	 */
	protected void registerTemplateDirectiveModel(Configuration config, ServletContext servletContext) throws Exception {
		
		applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
		
		if (applicationContext == null) {
			//没有使用Spring容器
			super.registerTemplateDirectiveModel(config, servletContext);
			return;
		}
		
		autoWiringFactory = findAutoWiringBeanFactory(this.applicationContext);
		String customDirectiveFile = servletContext.getInitParameter(Constants.CUSTOM_DIRECTIVE);
		
		if (StringUtils.isBlank(customDirectiveFile)) {
			customDirectiveFile = "classpath:com/demo/webapp/views/freemarker/directive/config/demo-config.xml";
		}
		ConfigurationManager manager = ConfigurationManager.getInstance(customDirectiveFile, applicationContext.getClassLoader());
		manager.load();
		//manager.getDirectiveGroupModelList();
		
		List<DirectiveGroupModel> groupModelList = manager.getDirectiveGroupModelList();
		for (DirectiveGroupModel groupModel : groupModelList) {
			String nameSpace = groupModel.getShortname();
			List<DirectiveModel> directiveModelList = groupModel.getDirectiveModelList();
			for (DirectiveModel model : directiveModelList) {
				String tagName = model.getName(); 
				String tagclass = model.getTagclass();
				
				Class<?> clazz = getClassInstance(tagclass);
				//指令对象
				Object object = buildBean(clazz, null);
				if (object instanceof TemplateDirectiveModel) {
					//自定义指令对象  nameSpace + "_" + tagName;
					String name = defineFullName(nameSpace, tagName);
					config.setSharedVariable(name, object);
					// 设置验证参数
					if (object instanceof ParameterValidateable) {
						((ParameterValidateable) object).setDirectiveModel(model);
						((ParameterValidateable) object).setName(name);
					}
				}
			}
		}
		//ClassLoaderUtils.loadClass(className, callingClass)
	}
	
	// ~~ ================================================================================================
    protected AutowireCapableBeanFactory findAutoWiringBeanFactory(ApplicationContext context) {
        if (context instanceof AutowireCapableBeanFactory) {
            // Check the context
            return (AutowireCapableBeanFactory) context;
        } else if (context instanceof ConfigurableApplicationContext) {
            //try and grab the beanFactory
            return ((ConfigurableApplicationContext) context).getBeanFactory();
        } else if (context.getParent() != null) {
            // And if all else fails, try again with the parent context
            return findAutoWiringBeanFactory(context.getParent());
        }
        return null;
    }
    
	public Object buildBean(Class<?> clazz, Map<?, ?> extraContext) throws Exception {
    	
		Object bean;
		
		if (autoWiringFactory == null) {
			autoWiringFactory = findAutoWiringBeanFactory(this.applicationContext);
		}
        try {
            // Decide to follow autowire strategy or use the legacy approach which mixes injection strategies
            if (alwaysRespectAutowireStrategy) {
                // Leave the creation up to Spring
                bean = autoWiringFactory.createBean(clazz, autowireStrategy, false);
                return bean;
            } else {
                bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
                bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());
                // We don't need to call the init-method since one won't be registered.
                bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());
                return autoWireBean(bean, autoWiringFactory);
            }
        } catch (UnsatisfiedDependencyException e) {
            if (Logger.isErrorEnabled())
            	Logger.error("Error building bean", e);
            // Fall back
            return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory);
        }
    }
	
    public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) {
        if (autoWiringFactory != null) {
            autoWiringFactory.autowireBeanProperties(bean, autowireStrategy, false);
        }
        return bean;
    }
    
	protected Class<?> getClassInstance(String className) throws ClassNotFoundException {
    	Class<?> clazz = null;
		if (useClassCache) {
			synchronized (classes) {
				// this cache of classes is needed because Spring sucks at
				// dealing with situations where the
				// class instance changes
				clazz = (Class<?>) classes.get(className);
			}
		}

		if (clazz == null) {
			if (applicationContext.containsBean(className)) {
				clazz = applicationContext.getBean(className).getClass();
			} else {
				clazz = super.getClassInstance(className);
			}

			if (useClassCache) {
				synchronized (classes) {
					classes.put(className, clazz);
				}
			}
		}

		return clazz;
	}
	
	
	
	// 指定定义命名规则
	protected String defineFullName(String nameSpace, String tagName) {
		return nameSpace + "_" + tagName;
	}
	
	public WebApplicationContext getApplicationContext() {
		return applicationContext;
	}

	public void setAutoWiringFactory(AutowireCapableBeanFactory autoWiringFactory) {
		this.autoWiringFactory = autoWiringFactory;
	}

	public void setAutowireStrategy(int autowireStrategy) {
		this.autowireStrategy = autowireStrategy;
	}

	public void setUseClassCache(boolean useClassCache) {
		this.useClassCache = useClassCache;
	}
}
